package com.xmzs.web.service.impl;

import cn.hutool.core.util.StrUtil;
import com.xmzs.common.chat.config.LocalCache;
import com.xmzs.common.chat.constant.OpenAIConst;
import com.xmzs.common.chat.domain.request.ChatRequest;
import com.xmzs.common.chat.entity.chat.*;
import com.xmzs.common.chat.entity.images.Image;
import com.xmzs.common.chat.entity.images.ImageResponse;
import com.xmzs.common.chat.entity.images.SizeEnum;
import com.xmzs.common.chat.listener.SSEEventSourceListener;
import com.xmzs.common.chat.openai.OpenAiStreamClient;
import com.xmzs.common.chat.utils.TikTokensUtil;
import com.xmzs.common.core.domain.model.LoginUser;
import com.xmzs.common.core.exception.base.BaseException;
import com.xmzs.common.oss.core.OssClient;
import com.xmzs.common.oss.entity.UploadResult;
import com.xmzs.common.oss.factory.OssFactory;
import com.xmzs.common.satoken.utils.LoginHelper;
import com.xmzs.system.domain.SysUser;
import com.xmzs.system.mapper.SysUserMapper;
import com.xmzs.web.service.ChatService;
import com.xmzs.web.service.SseService;
import jakarta.xml.bind.DatatypeConverter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.codec.binary.Base64;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

/**
 * 描述：
 *
 * @author https:www.unfbx.com
 * @date 2023-04-08
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class SseServiceImpl implements SseService {

    private final OpenAiStreamClient openAiStreamClient;

    private final ChatService chatService;

    private final SysUserMapper sysUserMapper;


    @Override
    public ResponseBodyEmitter sseChat(ChatRequest chatRequest) {

        ResponseBodyEmitter sseEmitter = getResponseBodyEmitter(chatRequest);
        SSEEventSourceListener openAIEventSourceListener = new SSEEventSourceListener(sseEmitter);
        // TODO 临时处理
        LoginUser loginUser = LoginHelper.getLoginUser();
        if (loginUser == null) {
            throw new BaseException("用户未登录！");
        }
        SysUser sysUser = sysUserMapper.selectById(loginUser.getUserId());
        if(sysUser.getUserBalance().compareTo(new BigDecimal(3))<0 && !ChatCompletion.Model.GPT_3_5_TURBO_0613.getName().equals(chatRequest.getModel())){
            try {
                sseEmitter.send("余额不足3元,请切换GPT3.5模型或者点击《进入市场选购您的商品》充值后使用!");
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            sseEmitter.complete();
            return sseEmitter;
        }

        if (StrUtil.isBlank(chatRequest.getPrompt())) {
            throw new BaseException("参数异常，msg不能为空~");
        }



        // 上下文信息
        List<Content> contentList = new ArrayList<>();
        LinkedList<Message> messages = new LinkedList<>();

        // DALL3 绘图模型
        if(Image.Model.DALL_E_3.getName().equals(chatRequest.getModel())){
            Image image = Image.builder()
                .responseFormat(com.xmzs.common.chat.entity.images.ResponseFormat.B64_JSON.getName())
                .model(Image.Model.DALL_E_3.getName())
                .prompt(chatRequest.getPrompt())
                .n(1)
                .quality(Image.Quality.STANDARD.getName())
                .size(SizeEnum.size_1024.getName())
                .style(Image.Style.VIVID.getName())
                .build();
            ImageResponse imageResponse = openAiStreamClient.genImages(image);
            String urlB64 = imageResponse.getData().get(0).getB64Json();
            String imageUrl=compressPicForScale(urlB64, 512);
            byte[] decode= DatatypeConverter.parseBase64Binary(imageUrl);
            OssClient storage = OssFactory.instance();
            UploadResult upload=storage.upload(decode, storage.getPath("panda",".png"), "image/png");
            try {
                sseEmitter.send("<img src='"+upload.getUrl()+"' width=360 height=300>");
                sseEmitter.complete();
                chatService.deductToken(chatRequest.getModel(), 0, BaseChatCompletion.ChatType.CHAT_IN.name(),chatRequest.getPrompt(),new BigDecimal(OpenAIConst.DALL3_COST));
            }catch (Exception e){
                log.info("图片生成失败!{}",e.getMessage());
            }
            return sseEmitter;
        }

        // 图文识别模型
        if(ChatCompletion.Model.GPT_4_VISION_PREVIEW.getName().equals(chatRequest.getModel())) {
            Content textContent = Content.builder().text(chatRequest.getPrompt()).type(Content.Type.TEXT.getName()).build();
            ImageUrl imageUrl = ImageUrl.builder().url(chatRequest.getImgurl()).build();
            Content imageContent = Content.builder().imageUrl(imageUrl).type(Content.Type.IMAGE_URL.getName()).build();
            contentList.add(textContent);
            contentList.add(imageContent);
            MessagePicture message = MessagePicture.builder().role(Message.Role.USER.getName()).content(contentList).build();
            ChatCompletionWithPicture chatCompletion = ChatCompletionWithPicture
                .builder()
                .messages(Collections.singletonList(message))
                .model(chatRequest.getModel())
                .user(chatRequest.getConversationId())
                .temperature(chatRequest.getTemperature())
                .topP(chatRequest.getTop_p())
                .stream(true)
                .build();
            openAiStreamClient.streamChatCompletion(chatCompletion, openAIEventSourceListener);
        }else {
            // 判断是否需要携带上下文信息
            if(chatRequest.getUsingContext()){
                // 获取对话记录
                messages = LocalCache.getUserChatMessages(chatRequest.getConversationId(), chatRequest.getContentNumber());
            }
            // 设置系统角色
            Message systemMessage = Message.builder().content(chatRequest.getSystemMessage()).role(Message.Role.SYSTEM).build();
            messages.addFirst(systemMessage);
            // 添加本次消息记录
            Message message = Message.builder().content(chatRequest.getPrompt()).role(Message.Role.USER).build();
            messages.add(message);
            ChatCompletion completion = ChatCompletion
                .builder()
                .messages(messages)
                .model(chatRequest.getModel())
                .user(chatRequest.getConversationId())
                .temperature(chatRequest.getTemperature())
                .topP(chatRequest.getTop_p())
                .stream(true)
                .build();
            openAiStreamClient.streamChatCompletion(completion, openAIEventSourceListener);
            LocalCache.MESSAGE.put(chatRequest.getConversationId(), messages);
        }

        // 扣除余额
        int tokens = TikTokensUtil.tokens(chatRequest.getModel(),  new ArrayList<>(messages));
        chatService.deductToken(chatRequest.getModel(), tokens, BaseChatCompletion.ChatType.CHAT_IN.name(),chatRequest.getPrompt(),BigDecimal.ZERO);

        return sseEmitter;
    }


    /**
     * 创建sseEmitter
     * @param chatRequest
     * @return
     */
    private static ResponseBodyEmitter getResponseBodyEmitter(ChatRequest chatRequest) {

        ResponseBodyEmitter sseEmitter = new ResponseBodyEmitter(0L);

        sseEmitter.onCompletion(() -> {
            log.info("[{}]结束连接...................", chatRequest.getConversationId());
            LocalCache.CACHE.remove(chatRequest.getConversationId());
        });

        //超时回调
        sseEmitter.onTimeout(() -> {
            log.error("[{}]连接超时...................", chatRequest.getConversationId());
        });

        //异常回调
        sseEmitter.onError(
            throwable -> {
                log.error("[{}]连接失败...................", chatRequest.getConversationId());
            }
        );

        LocalCache.CACHE.put(chatRequest.getConversationId(), sseEmitter);

        log.info("[{}]创建sse连接成功！", chatRequest.getConversationId());

        return sseEmitter;
    }

    /**
     * 根据指定大小压缩图片,并转成base64
     *
     * @param image  base64图片
     * @param desFileSize 指定图片大小，单位kb
     * @return 压缩质量后的图片字节数组
     */
    public static String compressPicForScale(String image, long desFileSize) {
        byte[] imageBytes = Base64.decodeBase64(image);
        if (imageBytes == null || imageBytes.length <= 0 || imageBytes.length < desFileSize * 1024) {
            return Base64.encodeBase64String(imageBytes);
        }
        long srcSize = imageBytes.length;
        double accuracy = getAccuracy(srcSize / 1024);
        try {
            while (imageBytes.length > desFileSize * 1024) {
                ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes);
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream(imageBytes.length);
                Thumbnails.of(inputStream)
                    .scale(accuracy)
                    .outputQuality(accuracy)
                    .toOutputStream(outputStream);
                imageBytes = outputStream.toByteArray();
            }
            log.info("【图片压缩】图片原大小={}kb | 压缩后大小={}kb", srcSize / 1024, imageBytes.length / 1024);
        } catch (Exception e) {
            log.error("【图片压缩】msg=图片压缩失败!", e);
        }

        return Base64.encodeBase64String(imageBytes);
    }


    /**
     * 自动调节精度(经验数值)
     *
     * @param size 源图片大小
     * @return 图片压缩质量比
     */
    public static double getAccuracy(long size) {
        double accuracy;
        if (size < 900) {
            accuracy = 0.85;
        } else if (size < 2047) {
            accuracy = 0.6;
        } else if (size < 3275) {
            accuracy = 0.44;
        } else {
            accuracy = 0.4;
        }
        return accuracy;
    }

}
